//! Naming convention utilities for converting between different naming styles
//! 
//! This module provides functionality to convert between snake_case, camelCase,
//! PascalCase, and other naming conventions commonly used in databases and GraphQL.

use regex::Regex;
use std::collections::HashMap;

/// Naming converter for different naming conventions
pub struct NamingConverter {
    /// Cache for converted names to improve performance
    cache: HashMap<String, String>,
    /// Custom naming rules
    custom_rules: HashMap<String, String>,
}

impl NamingConverter {
    /// Create a new naming converter
    pub fn new() -> Self {
        Self {
            cache: HashMap::new(),
            custom_rules: HashMap::new(),
        }
    }
    
    /// Add a custom naming rule
    pub fn add_custom_rule(&mut self, from: String, to: String) {
        self.custom_rules.insert(from, to);
    }
    
    /// Convert snake_case to camelCase
    pub fn snake_to_camel(&mut self, input: &str) -> String {
        if let Some(cached) = self.cache.get(input) {
            return cached.clone();
        }
        
        // Check custom rules first
        if let Some(custom) = self.custom_rules.get(input) {
            self.cache.insert(input.to_string(), custom.clone());
            return custom.clone();
        }
        
        let result = if input.is_empty() {
            String::new()
        } else {
            let parts: Vec<&str> = input.split('_').collect();
            let mut result = parts[0].to_lowercase();
            
            for part in &parts[1..] {
                if !part.is_empty() {
                    result.push_str(&Self::capitalize_first(part));
                }
            }
            
            result
        };
        
        self.cache.insert(input.to_string(), result.clone());
        result
    }
    
    /// Convert snake_case to PascalCase
    pub fn snake_to_pascal(&mut self, input: &str) -> String {
        if let Some(cached) = self.cache.get(&format!("pascal_{}", input)) {
            return cached.clone();
        }
        
        // Check custom rules first
        if let Some(custom) = self.custom_rules.get(input) {
            let pascal = Self::capitalize_first(custom);
            self.cache.insert(format!("pascal_{}", input), pascal.clone());
            return pascal;
        }
        
        let result = if input.is_empty() {
            String::new()
        } else {
            input
                .split('_')
                .filter(|part| !part.is_empty())
                .map(Self::capitalize_first)
                .collect::<Vec<String>>()
                .join("")
        };
        
        self.cache.insert(format!("pascal_{}", input), result.clone());
        result
    }
    
    /// Convert camelCase to snake_case
    pub fn camel_to_snake(&mut self, input: &str) -> String {
        if let Some(cached) = self.cache.get(&format!("snake_{}", input)) {
            return cached.clone();
        }
        
        let result = if input.is_empty() {
            String::new()
        } else {
            let re = Regex::new(r"([a-z0-9])([A-Z])").unwrap();
            re.replace_all(input, "${1}_${2}").to_lowercase()
        };
        
        self.cache.insert(format!("snake_{}", input), result.clone());
        result
    }
    
    /// Convert PascalCase to snake_case
    pub fn pascal_to_snake(&mut self, input: &str) -> String {
        self.camel_to_snake(input)
    }
    
    /// Convert table name to GraphQL type name
    pub fn table_to_type_name(&mut self, table_name: &str) -> String {
        // Remove common table prefixes/suffixes
        let cleaned = self.clean_table_name(table_name);
        
        // Convert to singular if it's plural
        let singular = self.pluralize_to_singular(&cleaned);
        
        // Convert to PascalCase
        self.snake_to_pascal(&singular)
    }
    
    /// Convert table name to GraphQL field name
    pub fn table_to_field_name(&mut self, table_name: &str) -> String {
        let cleaned = self.clean_table_name(table_name);
        self.snake_to_camel(&cleaned)
    }
    
    /// Convert column name to GraphQL field name
    pub fn column_to_field_name(&mut self, column_name: &str) -> String {
        self.snake_to_camel(column_name)
    }
    
    /// Convert foreign key column to relationship name
    pub fn foreign_key_to_relation_name(&mut self, fk_column: &str) -> String {
        // Remove common foreign key suffixes like "_id"
        let cleaned = if fk_column.ends_with("_id") {
            &fk_column[..fk_column.len() - 3]
        } else if fk_column.ends_with("Id") {
            &fk_column[..fk_column.len() - 2]
        } else {
            fk_column
        };
        
        self.snake_to_camel(cleaned)
    }
    
    /// Generate plural form for collections
    pub fn to_plural(&self, word: &str) -> String {
        if word.is_empty() {
            return String::new();
        }
        
        // Simple pluralization rules
        if word.ends_with('s') || word.ends_with("sh") || word.ends_with("ch") || word.ends_with('x') || word.ends_with('z') {
            format!("{}es", word)
        } else if word.ends_with('y') && word.len() > 1 {
            let before_y = word.chars().nth(word.len() - 2).unwrap();
            if !"aeiou".contains(before_y) {
                format!("{}ies", &word[..word.len() - 1])
            } else {
                format!("{}s", word)
            }
        } else if word.ends_with("fe") {
            format!("{}ves", &word[..word.len() - 2])
        } else if word.ends_with('f') {
            format!("{}ves", &word[..word.len() - 1])
        } else {
            format!("{}s", word)
        }
    }
    
    /// Convert plural to singular (simple heuristics)
    fn pluralize_to_singular(&self, word: &str) -> String {
        if word.is_empty() {
            return String::new();
        }
        
        // Simple singularization rules
        if word.ends_with("ies") && word.len() > 3 {
            format!("{}y", &word[..word.len() - 3])
        } else if word.ends_with("ves") && word.len() > 3 {
            format!("{}f", &word[..word.len() - 3])
        } else if word.ends_with("ses") || word.ends_with("shes") || word.ends_with("ches") || word.ends_with("xes") || word.ends_with("zes") {
            word[..word.len() - 2].to_string()
        } else if word.ends_with('s') && word.len() > 1 && !word.ends_with("ss") {
            word[..word.len() - 1].to_string()
        } else {
            word.to_string()
        }
    }
    
    /// Clean table name by removing common prefixes/suffixes
    fn clean_table_name(&self, table_name: &str) -> String {
        let mut cleaned = table_name.to_string();
        
        // Remove common prefixes
        for prefix in &["tbl_", "tb_", "t_"] {
            if cleaned.starts_with(prefix) {
                cleaned = cleaned[prefix.len()..].to_string();
                break;
            }
        }
        
        // Remove common suffixes
        for suffix in &["_table", "_tbl", "_tb"] {
            if cleaned.ends_with(suffix) {
                cleaned = cleaned[..cleaned.len() - suffix.len()].to_string();
                break;
            }
        }
        
        cleaned
    }
    
    /// Capitalize the first character of a string
    fn capitalize_first(s: &str) -> String {
        if s.is_empty() {
            String::new()
        } else {
            let mut chars = s.chars();
            match chars.next() {
                None => String::new(),
                Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
            }
        }
    }
    
    /// Clear the cache
    pub fn clear_cache(&mut self) {
        self.cache.clear();
    }
    
    /// Get cache size
    pub fn cache_size(&self) -> usize {
        self.cache.len()
    }
}

impl Default for NamingConverter {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_snake_to_camel() {
        let mut converter = NamingConverter::new();
        
        assert_eq!(converter.snake_to_camel("user_name"), "userName");
        assert_eq!(converter.snake_to_camel("first_name"), "firstName");
        assert_eq!(converter.snake_to_camel("id"), "id");
        assert_eq!(converter.snake_to_camel(""), "");
        assert_eq!(converter.snake_to_camel("user_profile_image"), "userProfileImage");
    }
    
    #[test]
    fn test_snake_to_pascal() {
        let mut converter = NamingConverter::new();
        
        assert_eq!(converter.snake_to_pascal("user_name"), "UserName");
        assert_eq!(converter.snake_to_pascal("first_name"), "FirstName");
        assert_eq!(converter.snake_to_pascal("id"), "Id");
        assert_eq!(converter.snake_to_pascal(""), "");
        assert_eq!(converter.snake_to_pascal("user_profile"), "UserProfile");
    }
    
    #[test]
    fn test_camel_to_snake() {
        let mut converter = NamingConverter::new();
        
        assert_eq!(converter.camel_to_snake("userName"), "user_name");
        assert_eq!(converter.camel_to_snake("firstName"), "first_name");
        assert_eq!(converter.camel_to_snake("id"), "id");
        assert_eq!(converter.camel_to_snake(""), "");
        assert_eq!(converter.camel_to_snake("userProfileImage"), "user_profile_image");
    }
    
    #[test]
    fn test_table_to_type_name() {
        let mut converter = NamingConverter::new();
        
        assert_eq!(converter.table_to_type_name("users"), "User");
        assert_eq!(converter.table_to_type_name("user_profiles"), "UserProfile");
        assert_eq!(converter.table_to_type_name("tbl_orders"), "Order");
        assert_eq!(converter.table_to_type_name("categories"), "Category");
    }
    
    #[test]
    fn test_foreign_key_to_relation_name() {
        let mut converter = NamingConverter::new();
        
        assert_eq!(converter.foreign_key_to_relation_name("user_id"), "user");
        assert_eq!(converter.foreign_key_to_relation_name("userId"), "user");
        assert_eq!(converter.foreign_key_to_relation_name("category_id"), "category");
        assert_eq!(converter.foreign_key_to_relation_name("parent_category_id"), "parentCategory");
    }
    
    #[test]
    fn test_pluralization() {
        let converter = NamingConverter::new();
        
        assert_eq!(converter.to_plural("user"), "users");
        assert_eq!(converter.to_plural("category"), "categories");
        assert_eq!(converter.to_plural("box"), "boxes");
        assert_eq!(converter.to_plural("child"), "childs"); // Simple rule, not perfect
        assert_eq!(converter.to_plural("knife"), "knives");
        assert_eq!(converter.to_plural("leaf"), "leaves");
    }
    
    #[test]
    fn test_singularization() {
        let converter = NamingConverter::new();
        
        assert_eq!(converter.pluralize_to_singular("users"), "user");
        assert_eq!(converter.pluralize_to_singular("categories"), "category");
        assert_eq!(converter.pluralize_to_singular("boxes"), "boxe"); // Simple rule
        assert_eq!(converter.pluralize_to_singular("knives"), "knivef"); // Simple rule
    }
    
    #[test]
    fn test_custom_rules() {
        let mut converter = NamingConverter::new();
        converter.add_custom_rule("special_table".to_string(), "CustomType".to_string());
        
        assert_eq!(converter.snake_to_camel("special_table"), "CustomType");
        assert_eq!(converter.snake_to_camel("normal_table"), "normalTable");
    }
    
    #[test]
    fn test_cache() {
        let mut converter = NamingConverter::new();
        
        // First call should populate cache
        converter.snake_to_camel("user_name");
        assert_eq!(converter.cache_size(), 1);
        
        // Second call should use cache
        converter.snake_to_camel("user_name");
        assert_eq!(converter.cache_size(), 1);
        
        // Different conversion should add to cache
        converter.snake_to_pascal("user_name");
        assert_eq!(converter.cache_size(), 2);
        
        // Clear cache
        converter.clear_cache();
        assert_eq!(converter.cache_size(), 0);
    }
}